0e841a7
[thunderbird.git] /
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include <locale.h>
8 #include "OSPreferences.h"
9 #include "dlfcn.h"
10 #include "glib.h"
11 #include "gio/gio.h"
12
13 #include "unicode/uloc.h"
14
15 using namespace mozilla::intl;
16
17 OSPreferences::OSPreferences() = default;
18
19 OSPreferences::~OSPreferences() = default;
20
21 bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) {
22   MOZ_ASSERT(aLocaleList.IsEmpty());
23
24   nsAutoCString defaultLang(uloc_getDefault());
25
26   if (CanonicalizeLanguageTag(defaultLang)) {
27     aLocaleList.AppendElement(defaultLang);
28     return true;
29   }
30   return false;
31 }
32
33 bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) {
34   MOZ_ASSERT(aLocaleList.IsEmpty());
35
36   // For now we're just taking the LC_TIME from POSIX environment for all
37   // regional preferences.
38   nsAutoCString localeStr(setlocale(LC_TIME, nullptr));
39
40   if (CanonicalizeLanguageTag(localeStr)) {
41     aLocaleList.AppendElement(localeStr);
42     return true;
43   }
44
45   return false;
46 }
47
48 /*
49  * This looks up into gtk settings for hourCycle format.
50  *
51  * This works for all GUIs that use gtk settings like Gnome, Elementary etc.
52  * Ubuntu does not use those settings so we'll want to support them separately.
53  *
54  * We're taking the current 12/24h settings irrelevant of the locale, because
55  * in the UI user selects this setting for all locales.
56  */
57 typedef GVariant* (*get_value_fn_t)(GSettings*, const gchar*);
58
59 static get_value_fn_t FindGetValueFunction() {
60   get_value_fn_t fn = reinterpret_cast<get_value_fn_t>(
61       dlsym(RTLD_DEFAULT, "g_settings_get_user_value"));
62   return fn ? fn : &g_settings_get_value;
63 }
64
65 static int HourCycle() {
66   int rval = 0;
67
68   const char* schema;
69   const char* key;
70   const char* env = getenv("XDG_CURRENT_DESKTOP");
71   if (env && strcmp(env, "Unity") == 0) {
72     schema = "com.canonical.indicator.datetime";
73     key = "time-format";
74   } else {
75     schema = "org.gnome.desktop.interface";
76     key = "clock-format";
77   }
78
79   // This is a workaround for old GTK versions.
80   // Once we bump the minimum version to 2.40 we should replace
81   // this with g_settings_schme_source_lookup.
82   // See bug 1356718 for details.
83   const char* const* schemas = g_settings_list_schemas();
84   GSettings* settings = nullptr;
85
86   for (uint32_t i = 0; schemas[i] != nullptr; i++) {
87     if (strcmp(schemas[i], schema) == 0) {
88       settings = g_settings_new(schema);
89       break;
90     }
91   }
92
93   if (settings) {
94     // We really want to use g_settings_get_user_value which will
95     // only want to take it if user manually changed the value.
96     // But this requires glib 2.40, and we still support older glib versions,
97     // so we have to check whether it's available and fall back to the older
98     // g_settings_get_value if not.
99     static get_value_fn_t sGetValueFunction = FindGetValueFunction();
100     GVariant* value = sGetValueFunction(settings, key);
101     if (value) {
102       if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) {
103         const char* strVal = g_variant_get_string(value, nullptr);
104         if (strncmp("12", strVal, 2) == 0) {
105           rval = 12;
106         } else if (strncmp("24", strVal, 2) == 0) {
107           rval = 24;
108         }
109       }
110       g_variant_unref(value);
111     }
112     g_object_unref(settings);
113   }
114   return rval;
115 }
116
117 /**
118  * Since Gtk does not provide a way to customize or format date/time patterns,
119  * we're reusing ICU data here, but we do modify it according to the only
120  * setting Gtk gives us - hourCycle.
121  *
122  * This means that for gtk we will return a pattern from ICU altered to
123  * represent h12/h24 hour cycle if the user modified the default value.
124  *
125  * In short, this should work like this:
126  *
127  *  * gtk defaults, pl: 24h
128  *  * gtk defaults, en: 12h
129  *
130  *  * gtk 12h, pl: 12h
131  *  * gtk 12h, en: 12h
132  *
133  *  * gtk 24h, pl: 24h
134  *  * gtk 12h, en: 12h
135  */
136 bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
137                                         DateTimeFormatStyle aTimeStyle,
138                                         const nsACString& aLocale,
139                                         nsAString& aRetVal) {
140   nsAutoString skeleton;
141   if (!GetDateTimeSkeletonForStyle(aDateStyle, aTimeStyle, aLocale, skeleton)) {
142     return false;
143   }
144
145   // Customize the skeleton if necessary to reflect user's 12/24hr pref
146   switch (HourCycle()) {
147     case 12: {
148       // If skeleton contains 'H' or 'k', replace with 'h' or 'K' respectively,
149       // and add 'a' unless already present.
150       if (skeleton.FindChar('H') == -1 && skeleton.FindChar('k') == -1) {
151         break;  // nothing to do
152       }
153       bool foundA = false;
154       for (size_t i = 0; i < skeleton.Length(); ++i) {
155         switch (skeleton[i]) {
156           case 'a':
157             foundA = true;
158             break;
159           case 'H':
160             skeleton.SetCharAt('h', i);
161             break;
162           case 'k':
163             skeleton.SetCharAt('K', i);
164             break;
165         }
166       }
167       if (!foundA) {
168         skeleton.Append(char16_t('a'));
169       }
170       break;
171     }
172     case 24:
173       // If skeleton contains 'h' or 'K', replace with 'H' or 'k' respectively,
174       // and delete 'a' if present.
175       if (skeleton.FindChar('h') == -1 && skeleton.FindChar('K') == -1) {
176         break;  // nothing to do
177       }
178       for (int32_t i = 0; i < int32_t(skeleton.Length()); ++i) {
179         switch (skeleton[i]) {
180           case 'a':
181             skeleton.Cut(i, 1);
182             --i;
183             break;
184           case 'h':
185             skeleton.SetCharAt('H', i);
186             break;
187           case 'K':
188             skeleton.SetCharAt('k', i);
189             break;
190         }
191       }
192       break;
193   }
194
195   if (!GetPatternForSkeleton(skeleton, aLocale, aRetVal)) {
196     return false;
197   }
198
199   return true;
200 }